/*
 * Copyright 2010 - 2011, The PLDesktop Development Team
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#ifndef PLPROCESSOR_H
#define PLPROCESSOR_H

#if defined(X86)
#include <machine/pc/processor/x86/processor.h>
#include <machine/pc/processor/x86/interruptmanager.h>
#endif

#include <machine/pc/processor/irqmanager.h>

#include <tools/PLFlags.h>
#include <tools/PLSingleton.h>
using namespace PLSDK;

/**
 * The global processor implementation.
 */
class PLProcessor : public PLSingleton<PLProcessor> {
	friend class PLSingleton<PLProcessor>;
	friend class PLProcessorPrivate;
public:
	/**
	 * Values for the control register 0.
	 */
	enum ControlRegister0 {
		/**
		 * If 1, system is in protected mode, else system is in real mode.
		 */
		CR0ProtectedMode			= 1 << 0,
		/**
		 * Controls interaction of WAIT/FWAIT instructions with TS flag in CR0.
		 */
		CR0MonitorCoProcessor	= 1 << 1,
		/**
		 * If set, no x87 floating point unit present, if clear, x87 FPU present.
		 */
		CR0Emulation				= 1 << 2,
		/**
		 * Allows saving x87 task context only after x87 instruction used after task switch.
		 */
		CR0TaskSwitched			= 1 << 3,
		/**
		 * On the 386, it allowed to specify whether the external math coprocessor was an 80287 or 80387.
		 */
		CR0ExtensionType			= 1 << 4,
		/**
		 * Enable internal x87 floating point error reporting when set, else enables PC style x87 error detection.
		 */
		CR0NumericError			= 1 << 5,
		/**
		 * Determines whether the CPU can write to pages marked read-only.
		 */
		CR0WriteProtecte			= 1 << 16,
		/**
		 * Alignment check enabled if AM set, AC flag (in EFLAGS register) set, and privilege level is 3.
		 */
		CR0AlignmentMask			= 1 << 18,
		/**
		 * Globally enables/disable write-back caching.
		 */
		CR0NotWriteThrough		= 1 << 29,
		/**
		 * Globally enables/disable the memory cache.
		 */
		CR0CacheDisable			= 1 << 30,
		/**
		 * If 1, enable paging and use the CR3 register, else disable paging.
		 */
		CR0Paging					= 1 << 31
	};
	PLDECLAREFLAGS(ControlRegister0Flags, ControlRegister0);
	/**
	 * Values for the control register 4.
	 */
	enum ControlRegister4 {
		/**
		 * If set, enables support for the virtual interrupt flag (VIF) in virtual-8086 mode.
		 */
		CR4Virtual8086ModeExtensions			= 1 << 0,
		/**
		 * If set, enables support for the virtual interrupt flag (VIF) in protected mode.
		 */
		CR4ProtectedModeVirtualInterrupts	= 1 << 1,
		/**
		 * If set, RDTSC instruction can only be executed when in ring 0, otherwise RDTSC
		 * can be used at any privilege level.
		 */
		CR4TimeStampDisable						= 1 << 2,
		/**
		 * Debugging Extensions.
		 */
		CR4DebuggingExtensions					= 1 << 3,
		/**
		 * If unset, page size is 4 KB, else page size is increased to 4 MB (or with PAE set, 2 MB).
		 */
		CR4PageSizeExtensions					= 1 << 4,
		/**
		 * If set, changes page table layout to translate 32-bit virtual addresses into extended
		 * 36-bit physical addresses.
		 */
		CR4PhysicalAddressExtension			= 1 << 5,
		/**
		 * If set, enables machine check interrupts to occur.
		 */
		CR4MachineCheckException				= 1 << 6,
		/**
		 * If set, address translations (PDE or PTE records) may be shared between address spaces.
		 */
		CR4PageGlobalEnabled						= 1 << 7,
		/**
		 * If set, RDPMC can be executed at any privilege level, else RDPMC can only be used in ring 0.
		 */
		CR4PerformanceMonitoringCounter		= 1 << 8,
		/**
		 * If set, enables SSE instructions and fast FPU save & restore.
		 */
		CR4FXSAVEandFXSTOR						= 1 << 9,
		/**
		 * If set, enables unmasked SSE exceptions.
		 */
		CR4SIMDFloatingPointExceptions		= 1 << 10,
		/**
		 * VMX Enable.
		 */
		CR4VMXEnable								= 1 << 13,
		/**
		 * SMX Enable.
		 */
		CR4SMXEnable								= 1 << 14,
		/**
		 * If set, enables process-context identifiers (PCIDs).
		 */
		CR4PCIDEnable								= 1 << 17,
		/**
		 * XSAVE and Processor Extended States Enable.
		 */
		CR4XSAVE										= 1 << 18
	};
	PLDECLAREFLAGS(ControlRegister4Flags, ControlRegister4);
public:
	/**
	 * Init the processor.
	 */
	void start();
	/**
	 * Start other things after the memory management was started.
	 */
	void start1();
	/**
	 * Set a flag for the control register 0.
	 *
	 * @param flag The flag to set.
	 */
	void setControlRegister0(PLProcessor::ControlRegister0 flag);
	/**
	 * Get the flags that are set in the control register 0.
	 *
	 * @return The flags.
	 */
	inline PLProcessor::ControlRegister0Flags controlRegister0();
	/**
	 * Get the value from the control register 0.
	 *
	 * @return The value.
	 */
	uint32_t controlRegister0Value();
	/**
	 * Get the value from the control register 2.
	 *
	 * @return The value.
	 */
	uint32_t controlRegister2Value();
	/**
	 * Get the value from the control register 3.
	 *
	 * @return The value.
	 */
	uint32_t controlRegister3Value();
	/**
	 * Set a flag for the control register 4.
	 *
	 * @param flag The flag to set.
	 */
	void setControlRegister4(PLProcessor::ControlRegister4 flag);
	/**
	 * Get the flags that are set in the control register 4.
	 *
	 * @return The flags.
	 */
	inline PLProcessor::ControlRegister4Flags controlRegister4();
	/**
	 * Get the value from the control register 4.
	 *
	 * @return The value.
	 */
	uint32_t controlRegister4Value();
	/**
	 * Get the processor state of a processor.
	 *
	 * @param processor The processor id of the processor state.
	 * @return The processor states.
	 */
	ProcessorState &processorState(size_t processor = 0);
	/**
	 * Get the interrupt manager.
	 *
	 * @return The interrupt manager.
	 */
	PLInterruptManager &interruptManager();
	/**
	 * Get the irq manager of the system.
	 *
	 * @return The irq manager.
	 */
	inline PLIrqManager *irqManager();
	/**
	 * Disable or enable interrupts.
	 *
	 * @param enabled True to enable interrupts.
	 */
	void setInterrupts(bool enabled);
	/**
	 * Is interrupt handling enabled.
	 *
	 * @return True if interrupt handling was enabled.
	 */
	bool interrupts();
	/**
	 * Halt CPU execution.
	 */
	void halt();
	/**
	 * Read a byte (8bit) from the I/O Port or the memory-mapped I/O region.
	 *
	 * @param offset offset from the I/O base port or the I/O base memory address.
	 * @return the byte (8bit) that have been read.
	 */
	inline uint8_t in8(size_t offset);
	/**
	 * Read two byte (16bit) from the I/O Port or the memory-mapped I/O region.
	 *
	 * @param offset offset from the I/O base port or the I/O base memory address.
	 * @return the two byte (16bit) that have been read.
	 */
	inline uint16_t in16(size_t offset);
	/**
	 * Read four byte (32bit) from the I/O Port or the memory-mapped I/O region.
	 *
	 * @param offset offset from the I/O base port or the I/O base memory address.
	 * @return the four byte (32bit) that have been read.
	 */
	inline uint32_t in32(size_t offset);
	/**
	 * Write a byte (8bit) to the I/O port or the memory-mapped I/O region.
	 *
	 * @param value the value that should be written.
	 * @param offset offset from the I/O base port or the I/O base memory address.
	 */
	inline void out8(uint8_t value, size_t offset);
	/**
	 * Write two byte (16bit) to the I/O port or the memory-mapped I/O region.
	 *
	 * @param value the value that should be written.
	 * @param offset offset from the I/O base port or the I/O base memory address.
	 */
	inline void out16(uint16_t value, size_t offset);
	/**
	 * Write four byte (32bit) to the I/O port or the memory-mapped I/O region.
	 *
	 * @param value the value that should be written.
	 * @param offset offset from the I/O base port or the I/O base memory address.
	 */
	inline void out32(uint32_t value, size_t offset);
protected:
	/**
	 * Constrcutor.
	 */
	PLProcessor();
private:
	PLProcessorPrivate m_processor;
	ProcessorState *m_processorStates;
	PLInterruptManager m_interruptManager;
	PLIrqManager *m_irqManager;
	uint32_t m_controlRegister0;
	uint32_t m_controlRegister4;
};

PLProcessor::ControlRegister0Flags PLProcessor::controlRegister0() {
	return PLProcessor::ControlRegister0Flags(controlRegister0Value());
}

PLProcessor::ControlRegister4Flags PLProcessor::controlRegister4() {
	return PLProcessor::ControlRegister4Flags(controlRegister4Value());
}

PLIrqManager *PLProcessor::irqManager() {
	return m_irqManager;
}

uint8_t PLProcessor::in8(size_t offset) {
	return m_processor.in8(offset);
}

uint16_t PLProcessor::in16(size_t offset) {
	return m_processor.in16(offset);
}

uint32_t PLProcessor::in32(size_t offset) {
	return m_processor.in32(offset);
}

void PLProcessor::out8(uint8_t value, size_t offset) {
	m_processor.out8(value, offset);
}

void PLProcessor::out16(uint16_t value, size_t offset) {
	m_processor.out16(value, offset);
}

void PLProcessor::out32(uint32_t value, size_t offset) {
	m_processor.out32(value, offset);
}

#endif // PLPROCESSOR_H
